using System;
using System.Collections;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.EventSystems;
using UnityEngine.UI;

/*
* Introduction：    循环滚动视图基类，提供核心的循环滚动功能和基础接口
* Version：         1.1
* Creator：         yixiang.hui
* Creation Time：   2025.9.3
 
* Last Editor：     yixiang.hui
* Last Edit Time：  2025.9.3
* Note：            1.1版本添加自动吸附功能，优化性能并修复了多个潜在bug
*/

namespace Hyx.UIComponent
{
    public delegate void UpdateItemHandler(LoopViewPoolCell item, int index);
    public delegate void OnDragFinishHandler();
    public delegate void OnDragEndHandler();
    public delegate void OnScrollReadyHandler();

    [SelectionBase]
    [ExecuteInEditMode]
    [DisallowMultipleComponent]
    [RequireComponent(typeof(RectTransform))]
    public abstract class LoopScrollView : UIBehaviour, IInitializePotentialDragHandler, IBeginDragHandler, IEndDragHandler, IDragHandler, IScrollHandler, ICanvasElement, ILayoutElement, ILayoutGroup, IPointerEnterHandler, IPointerExitHandler
    {
        public LoopViewPool prefabPool;

        public int totalCount;
        [HideInInspector]
        public bool initInStart = false;

        public float threshold = 100;

        public bool reverseDirection = false;

        public UpdateItemHandler ItemShowHandler = null;

        public OnDragFinishHandler onDragFinished = null;

        public OnDragEndHandler onDragEnded = null;

        public OnScrollReadyHandler onReady = null;

        public Func<Vector2, bool> canDrag;

        protected int itemTypeStart = 0;
        protected int itemTypeEnd = 0;

        [SerializeField]
        private Transform beforeTransform = default;
        [SerializeField]
        private Transform afterTransform = default;

        protected abstract float GetSize(RectTransform item);
        protected abstract float GetDimension(Vector2 vector);
        protected abstract Vector2 GetVector(float value);
        protected int directionSign = 0;

        private float m_ContentSpacing = -1;
        protected float contentSpacing
        {
            get
            {
                if (m_ContentSpacing >= 0)
                {
                    return m_ContentSpacing;
                }
                m_ContentSpacing = 0;
                if (content != null)
                {
                    HorizontalOrVerticalLayoutGroup layout1 = content.GetComponent<HorizontalOrVerticalLayoutGroup>();
                    if (layout1 != null)
                    {
                        m_ContentSpacing = layout1.spacing;
                    }
                    else
                    {
                        GridLayoutGroup layout2 = content.GetComponent<GridLayoutGroup>();
                        if (layout2 != null)
                        {
                            m_ContentSpacing = GetDimension(layout2.spacing);
                        }
                    }
                }
                return m_ContentSpacing;
            }
        }

        private int m_ContentConstraintCount = 0;
        protected int contentConstraintCount
        {
            get
            {
                if (m_ContentConstraintCount > 0)
                {
                    return m_ContentConstraintCount;
                }
                m_ContentConstraintCount = 1;
                if (content != null)
                {
                    GridLayoutGroup layout2 = content.GetComponent<GridLayoutGroup>();
                    if (layout2 != null)
                    {
                        if (layout2.constraint == GridLayoutGroup.Constraint.Flexible)
                        {
                            Debug.LogWarning("[LoopScrollRect] Flexible not supported yet");
                        }
                        m_ContentConstraintCount = layout2.constraintCount;
                    }
                }
                return m_ContentConstraintCount;
            }
        }

        public bool IsFull
        {
            get
            {
                if (itemTypeEnd >= totalCount)
                    return true;
                return false;
            }
        }

        protected virtual bool UpdateItems(Bounds viewBounds, Bounds contentBounds) { return false; }
        //==========LoopScrollRect==========

        public enum MovementType
        {
            Unrestricted, // 无边界吸附
            Elastic, // 边界吸附
            Clamped, // 边界禁止
        }

        public enum ScrollbarVisibility
        {
            Permanent,
            AutoHide,
            AutoHideAndExpandViewport,
        }

        [Serializable]
        public struct AutoScrollProperties
        {
            public bool enable;
            public Vector2 speed;
        }

        [SerializeField, Tooltip("自动轮播")]
        public AutoScrollProperties autoScrollProperties = new AutoScrollProperties { enable = false, speed = Vector2.one };

        public bool lastAutoScroll = false;

        [Serializable]
        public struct ScrollToCenterProperties
        {
            public bool enable;
            public float speed;
            public float maxMoveDistance;
        }

        [SerializeField, Tooltip("子元素自动居中")]
        public ScrollToCenterProperties scrollToCenterProperties = new ScrollToCenterProperties { enable = false, speed = 100f, maxMoveDistance = 50f };

        private Coroutine scrollToCenterCoroutine;

        [Serializable]
        public class ScrollRectEvent : UnityEvent<Vector2> { }

        [SerializeField]
        private RectTransform m_Content;
        public RectTransform content { get { return m_Content; } set { m_Content = value; } }

        [SerializeField]
        private bool m_Horizontal = true;
        public bool horizontal { get { return m_Horizontal; } set { m_Horizontal = value; } }

        [SerializeField]
        private bool m_Vertical = true;
        public bool vertical { get { return m_Vertical; } set { m_Vertical = value; } }

        [SerializeField]
        private MovementType m_MovementType = MovementType.Elastic;
        public MovementType movementType { get { return m_MovementType; } set { m_MovementType = value; } }

        [SerializeField]
        private float m_Elasticity = 0.1f;
        public float elasticity { get { return m_Elasticity; } set { m_Elasticity = value; } }

        [SerializeField]
        private bool m_Inertia = true;
        public bool inertia { get { return m_Inertia; } set { m_Inertia = value; } }

        [SerializeField]
        private float m_DecelerationRate = 0.135f;
        public float decelerationRate { get { return m_DecelerationRate; } set { m_DecelerationRate = value; } }

        [SerializeField]
        private float m_ScrollSensitivity = 1.0f;
        public float scrollSensitivity { get { return m_ScrollSensitivity; } set { m_ScrollSensitivity = value; } }

        [SerializeField]
        private RectTransform m_Viewport;
        public RectTransform viewport { get { return m_Viewport; } set { m_Viewport = value; SetDirtyCaching(); } }

        //[SerializeField]
        //private float m_AutoMove = 0;
        //public float autoMove { get { return m_AutoMove; } set { m_AutoMove = value; } }

        [SerializeField]
        private Scrollbar m_HorizontalScrollbar;
        public Scrollbar horizontalScrollbar
        {
            get
            {
                return m_HorizontalScrollbar;
            }
            set
            {
                if (m_HorizontalScrollbar)
                    m_HorizontalScrollbar.onValueChanged.RemoveListener(SetHorizontalNormalizedPosition);
                m_HorizontalScrollbar = value;
                if (m_HorizontalScrollbar)
                    m_HorizontalScrollbar.onValueChanged.AddListener(SetHorizontalNormalizedPosition);
                SetDirtyCaching();
            }
        }

        [SerializeField]
        private Scrollbar m_VerticalScrollbar;
        public Scrollbar verticalScrollbar
        {
            get
            {
                return m_VerticalScrollbar;
            }
            set
            {
                if (m_VerticalScrollbar)
                    m_VerticalScrollbar.onValueChanged.RemoveListener(SetVerticalNormalizedPosition);
                m_VerticalScrollbar = value;
                if (m_VerticalScrollbar)
                    m_VerticalScrollbar.onValueChanged.AddListener(SetVerticalNormalizedPosition);
                SetDirtyCaching();
            }
        }

        [SerializeField]
        private ScrollbarVisibility m_HorizontalScrollbarVisibility;
        public ScrollbarVisibility horizontalScrollbarVisibility { get { return m_HorizontalScrollbarVisibility; } set { m_HorizontalScrollbarVisibility = value; SetDirtyCaching(); } }

        [SerializeField]
        private ScrollbarVisibility m_VerticalScrollbarVisibility;
        public ScrollbarVisibility verticalScrollbarVisibility { get { return m_VerticalScrollbarVisibility; } set { m_VerticalScrollbarVisibility = value; SetDirtyCaching(); } }

        [SerializeField]
        private float m_HorizontalScrollbarSpacing;
        public float horizontalScrollbarSpacing { get { return m_HorizontalScrollbarSpacing; } set { m_HorizontalScrollbarSpacing = value; SetDirty(); } }

        [SerializeField]
        private float m_VerticalScrollbarSpacing;
        public float verticalScrollbarSpacing { get { return m_VerticalScrollbarSpacing; } set { m_VerticalScrollbarSpacing = value; SetDirty(); } }

        [SerializeField]
        private ScrollRectEvent m_OnValueChanged = new ScrollRectEvent();
        public ScrollRectEvent onValueChanged { get { return m_OnValueChanged; } set { m_OnValueChanged = value; } }

        private Vector2 m_PointerStartLocalCursor = Vector2.zero;
        private Vector2 m_ContentStartPosition = Vector2.zero;

        private RectTransform m_ViewRect;

        private RectTransform m_thisRect;
        protected RectTransform thisRect
        {
            get
            {
                if (m_thisRect == null)
                    m_thisRect = transform.GetComponent<RectTransform>();
                return m_thisRect;
            }
        }

        protected RectTransform viewRect
        {
            get
            {
                if (m_ViewRect == null)
                    m_ViewRect = m_Viewport;
                if (m_ViewRect == null)
                    m_ViewRect = (RectTransform)transform;
                return m_ViewRect;
            }
        }

        protected Bounds m_ContentBounds;
        protected Bounds m_ViewBounds;

        private Vector2 m_Velocity;
        public Vector2 velocity { get { return m_Velocity; } set { m_Velocity = value; } }

        private bool m_Dragging;

        private Vector2 m_PrevPosition = Vector2.zero;
        private Bounds m_PrevContentBounds;
        private Bounds m_PrevViewBounds;
        [NonSerialized]
        private bool m_HasRebuiltLayout = false;

        private bool m_HSliderExpand;
        private bool m_VSliderExpand;
        private float m_HSliderHeight;
        private float m_VSliderWidth;

        [System.NonSerialized]
        private RectTransform m_Rect;
        private RectTransform rectTransform
        {
            get
            {
                if (m_Rect == null)
                    m_Rect = GetComponent<RectTransform>();
                return m_Rect;
            }
        }

        private RectTransform m_HorizontalScrollbarRect;
        private RectTransform m_VerticalScrollbarRect;

        private DrivenRectTransformTracker m_Tracker;

        protected LoopScrollView()
        {
            flexibleWidth = -1;
        }

        //==========LoopScrollRect==========
        protected override void Start()
        {
            base.Start();
            lastAutoScroll = autoScrollProperties.enable;
            if (initInStart == false)
            {
                RefillCells();
            }
        }

        protected virtual void Update()
        {
            if (autoScrollProperties.enable)
            {
                OnAutoScroll(autoScrollProperties.speed);
            }
        }

        /// <summary>
        /// 自动轮播
        /// </summary>
        /// <param name="delta"></param>
        public virtual void OnAutoScroll(Vector2 delta)
        {
            if (!IsActive())
                return;

            EnsureLayoutHasRebuilt();
            UpdateBounds();

            if (vertical && !horizontal)
            {
                delta.x = 0;
            }
            if (horizontal && !vertical)
            {
                delta.y = 0;
            }

            Vector2 position = m_Content.anchoredPosition;
            position += delta * m_ScrollSensitivity;
            if (m_MovementType == MovementType.Clamped)
                position += CalculateOffset(position - m_Content.anchoredPosition);

            SetContentAnchoredPosition(position);
            UpdateBounds();
        }

        #region 自动吸附功能

        /// <summary>
        /// 滚动到最近的项目
        /// </summary>
        public void ScrollToCenter()
        {
            if (totalCount == 0 || itemTypeEnd <= itemTypeStart || scrollToCenterProperties.speed <= 0)
            {
                return;
            }

            if (scrollToCenterCoroutine != null)
            {
                StopCoroutine(scrollToCenterCoroutine);
            }
            scrollToCenterCoroutine = StartCoroutine(ScrollToCenterCoroutine());
        }

        /// <summary>
        /// 找到距离视图中心最近的项目索引
        /// </summary>
        /// <returns></returns>
        private int FindClosestIndexToCenter()
        {
            m_ViewBounds = new Bounds(viewRect.rect.center, viewRect.rect.size);
            int closestIndex = itemTypeStart;
            float closestDistance = float.MaxValue;

            for (int i = itemTypeStart; i < itemTypeEnd; i++)
            {
                int childIdx = i - itemTypeStart;

                if (childIdx >= 0 && childIdx < content.childCount)
                {
                    var itemBounds = GetBounds4Item(i);

                    float itemDistance = float.MaxValue;
                    if (directionSign == -1) // 垂直滚动
                        itemDistance = Mathf.Abs(m_ViewBounds.center.y - itemBounds.center.y);
                    else if (directionSign == 1) // 水平滚动
                        itemDistance = Mathf.Abs(itemBounds.center.x - m_ViewBounds.center.x);

                    if (itemDistance < closestDistance)
                    {
                        closestIndex = i;
                        closestDistance = itemDistance;
                    }
                }
            }

            return closestIndex;
        }

        /// <summary>
        /// 获取指定索引项目的边界
        /// </summary>
        /// <param name="index"></param>
        /// <returns></returns>
        private Bounds GetBounds4Item(int index)
        {
            if (index < itemTypeStart || index >= itemTypeEnd)
                return new Bounds();

            int childIndex = index - itemTypeStart;
            if (childIndex < 0 || childIndex >= content.childCount)
                return new Bounds();

            RectTransform item = content.GetChild(childIndex) as RectTransform;
            if (item == null)
                return new Bounds();

            return GetBounds(item);
        }

        /// <summary>
        /// 获取指定RectTransform的边界
        /// </summary>
        /// <param name="item"></param>
        /// <returns></returns>
        private Bounds GetBounds(RectTransform item)
        {
            var vMin = new Vector3(float.MaxValue, float.MaxValue, float.MaxValue);
            var vMax = new Vector3(float.MinValue, float.MinValue, float.MinValue);

            var toLocal = viewRect.worldToLocalMatrix;
            item.GetWorldCorners(m_Corners);
            for (int j = 0; j < 4; j++)
            {
                Vector3 v = toLocal.MultiplyPoint3x4(m_Corners[j]);
                vMin = Vector3.Min(v, vMin);
                vMax = Vector3.Max(v, vMax);
            }

            var bounds = new Bounds(vMin, Vector3.zero);
            bounds.Encapsulate(vMax);
            return bounds;
        }

        /// <summary>
        /// 滚动到中心的协程
        /// </summary>
        /// <returns></returns>
        private IEnumerator ScrollToCenterCoroutine()
        {
            int centerIndex = FindClosestIndexToCenter();

            bool needMoving = true;
            while (needMoving)
            {
                yield return null;
                if (!m_Dragging)
                {
                    float move = 0;
                    if (centerIndex < itemTypeStart)
                    {
                        move = -Time.deltaTime * scrollToCenterProperties.speed;
                    }
                    else if (centerIndex >= itemTypeEnd)
                    {
                        move = Time.deltaTime * scrollToCenterProperties.speed;
                    }
                    else
                    {
                        m_ViewBounds = new Bounds(viewRect.rect.center, viewRect.rect.size);
                        var m_ItemBounds = GetBounds4Item(centerIndex);
                        var offset = 0.0f;
                        if (directionSign == -1) // 垂直滚动
                            offset = m_ViewBounds.center.y - m_ItemBounds.center.y;
                        else if (directionSign == 1) // 水平滚动
                            offset = m_ItemBounds.center.x - m_ViewBounds.center.x;

                        float maxMove = Time.deltaTime * scrollToCenterProperties.speed;
                        if (Mathf.Abs(offset) < maxMove)
                        {
                            needMoving = false;
                            move = offset;
                        }
                        else
                            move = Mathf.Sign(offset) * maxMove;
                    }

                    if (move != 0)
                    {
                        Vector2 offsetVector = GetVector(move);
                        content.anchoredPosition += offsetVector;
                        m_PrevPosition += offsetVector;
                        m_ContentStartPosition += offsetVector;
                    }
                }
                else
                {
                    // 如果用户开始拖动，停止自动滚动
                    needMoving = false;
                }
            }
            StopMovement();
            UpdatePrevData();
            scrollToCenterCoroutine = null;
        }

        #endregion

        public int GetIndex(RectTransform rect)
        {
            int siblingindex = rect.GetSiblingIndex();
            return itemTypeStart + siblingindex;
        }

        public RectTransform GetRect(int index)
        {
            if (index < itemTypeStart || index >= itemTypeEnd || content.childCount == 0)
                return null;

            int childIndex = index - itemTypeStart;
            if (childIndex < 0 || childIndex >= content.childCount)
                return null;

            return content.GetChild(childIndex).GetComponent<RectTransform>();
        }

        public LoopViewPoolCell GetLoopCell(int index)
        {
            if (index < itemTypeStart || index >= itemTypeEnd || content.childCount == 0)
                return null;

            int childIndex = index - itemTypeStart;
            if (childIndex < 0 || childIndex >= content.childCount)
                return null;

            return content.GetChild(childIndex).GetComponent<LoopViewPoolCell>();
        }

        public LoopViewPoolCell GetContentChild(int siblingIndex)
        {
            if (siblingIndex < 0 || siblingIndex >= content.childCount)
                return null;

            LoopViewPoolCell rectTrans = content.GetChild(siblingIndex).GetComponent<LoopViewPoolCell>();
            return rectTrans;
        }

        public void ClearCells()
        {
            if (Application.isPlaying)
            {
                itemTypeStart = 0;
                itemTypeEnd = 0;
                totalCount = 0;

                // 停止所有协程
                if (scrollToCenterCoroutine != null)
                {
                    StopCoroutine(scrollToCenterCoroutine);
                    scrollToCenterCoroutine = null;
                }

                // 使用Transform.DestroyImmediate而不是直接操作Transform
                while (content.childCount > 0)
                {
                    Transform child = content.GetChild(0);
                    child.SetParent(null);
                    prefabPool.PutItem<LoopViewPoolCell>(child.gameObject);
                }
            }
        }

        public void RefillCells(int startIdx = 0)
        {
            if (Application.isPlaying)
            {
                initInStart = false;
                StopMovement();

                // 停止所有协程
                if (scrollToCenterCoroutine != null)
                {
                    StopCoroutine(scrollToCenterCoroutine);
                    scrollToCenterCoroutine = null;
                }

                itemTypeStart = reverseDirection ? totalCount - startIdx : startIdx;
                itemTypeEnd = itemTypeStart;

                while (content.childCount > 0)
                {
                    Transform child = content.GetChild(0);
                    child.SetParent(null);
                    prefabPool.PutItem<LoopViewPoolCell>(child.gameObject);
                }
                BoundsCell();

                initInStart = true;

                onReady?.Invoke();
            }
        }

        protected void BoundsCell()
        {
            if (content.childCount > 0)
            {
                Vector2 pos = content.anchoredPosition;
                if (directionSign == -1)
                    pos.y = 0;
                else if (directionSign == 1)
                    pos.x = 0;
                content.anchoredPosition = pos;
                UpdateBounds();

                // 只在必要时强制更新Canvas
                if (content.childCount > 5)
                {
                    Canvas.ForceUpdateCanvases();
                }
            }
        }

        protected float NewItemAtStart()
        {
            if (totalCount >= 0 && itemTypeStart - contentConstraintCount < 0)
            {
                return 0;
            }
            float size = 0;
            for (int i = 0; i < contentConstraintCount; i++)
            {
                itemTypeStart--;
                RectTransform newItem = InstantiateNextItem(itemTypeStart).cacheRectTransform;
                newItem.SetAsFirstSibling();
                size = Mathf.Max(GetSize(newItem), size);
            }

            if (!reverseDirection)
            {
                Vector2 offset = GetVector(size);
                content.anchoredPosition += offset;
                m_PrevPosition += offset;
                m_ContentStartPosition += offset;
            }
            return size;
        }

        protected float DeleteItemAtStart()
        {
            if ((totalCount >= 0 && itemTypeEnd >= totalCount - 1) || content.childCount == 0)
            {
                return 0;
            }
            float size = 0;
            for (int i = 0; i < contentConstraintCount; i++)
            {
                RectTransform oldItem = content.GetChild(0) as RectTransform;
                size = Mathf.Max(GetSize(oldItem), size);
                prefabPool.PutItem<LoopViewPoolCell>(oldItem.gameObject);

                itemTypeStart++;

                if (content.childCount == 0)
                {
                    break;
                }
            }

            if (!reverseDirection)
            {
                Vector2 offset = GetVector(size);
                content.anchoredPosition -= offset;
                m_PrevPosition -= offset;
                m_ContentStartPosition -= offset;
            }
            return size;
        }


        protected float NewItemAtEnd()
        {
            if (totalCount >= 0 && itemTypeEnd >= totalCount)
            {
                return 0;
            }
            float size = 0;
            int si = itemTypeEnd % contentConstraintCount;
            for (int i = si; i < contentConstraintCount; i++)
            {
                RectTransform newItem = InstantiateNextItem(itemTypeEnd).cacheRectTransform;
                size = Mathf.Max(GetSize(newItem), size);
                itemTypeEnd++;
                if (totalCount >= 0 && itemTypeEnd >= totalCount)
                {
                    break;
                }
            }

            if (reverseDirection)
            {
                Vector2 offset = GetVector(size);
                content.anchoredPosition -= offset;
                m_PrevPosition -= offset;
                m_ContentStartPosition -= offset;
            }
            return size;
        }

        protected float DeleteItemAtEnd()
        {
            if ((totalCount >= 0 && itemTypeStart < contentConstraintCount) || content.childCount == 0)
            {
                return 0;
            }
            float size = 0;
            for (int i = 0; i < contentConstraintCount; i++)
            {
                RectTransform oldItem = content.GetChild(content.childCount - 1) as RectTransform;
                size = Mathf.Max(GetSize(oldItem), size);

                prefabPool.PutItem<LoopViewPoolCell>(oldItem.gameObject);

                itemTypeEnd--;
                if (itemTypeEnd % contentConstraintCount == 0 || content.childCount == 0)
                {
                    break;
                }
            }

            if (reverseDirection)
            {
                Vector2 offset = GetVector(size);
                content.anchoredPosition += offset;
                m_PrevPosition += offset;
                m_ContentStartPosition += offset;
            }
            return size;
        }

        protected LoopViewPoolCell InstantiateNextItem(int itemIdx)
        {
            if (prefabPool == null)
                return null;
            LoopViewPoolCell nextItem = prefabPool.FetchItem<LoopViewPoolCell>();
            nextItem.gameObject.SetActive(true);
            nextItem.cacheTransform.SetParent(content, false);
            ItemShowHandler?.Invoke(nextItem, itemIdx);

            return nextItem;
        }
        //==========LoopScrollRect==========

        public virtual void Rebuild(CanvasUpdate executing)
        {
            if (executing == CanvasUpdate.Prelayout)
            {
                UpdateCachedData();
            }

            if (executing == CanvasUpdate.PostLayout)
            {
                UpdateBounds(false);
                UpdateScrollbars(Vector2.zero);
                UpdatePrevData();

                m_HasRebuiltLayout = true;
            }
        }

        public virtual void LayoutComplete()
        { }

        public virtual void GraphicUpdateComplete()
        { }

        void UpdateCachedData()
        {
            Transform transform = this.transform;
            m_HorizontalScrollbarRect = m_HorizontalScrollbar == null ? null : m_HorizontalScrollbar.transform as RectTransform;
            m_VerticalScrollbarRect = m_VerticalScrollbar == null ? null : m_VerticalScrollbar.transform as RectTransform;

            bool viewIsChild = (viewRect.parent == transform);
            bool hScrollbarIsChild = (!m_HorizontalScrollbarRect || m_HorizontalScrollbarRect.parent == transform);
            bool vScrollbarIsChild = (!m_VerticalScrollbarRect || m_VerticalScrollbarRect.parent == transform);
            bool allAreChildren = (viewIsChild && hScrollbarIsChild && vScrollbarIsChild);

            m_HSliderExpand = allAreChildren && m_HorizontalScrollbarRect && horizontalScrollbarVisibility == ScrollbarVisibility.AutoHideAndExpandViewport;
            m_VSliderExpand = allAreChildren && m_VerticalScrollbarRect && verticalScrollbarVisibility == ScrollbarVisibility.AutoHideAndExpandViewport;
            m_HSliderHeight = (m_HorizontalScrollbarRect == null ? 0 : m_HorizontalScrollbarRect.rect.height);
            m_VSliderWidth = (m_VerticalScrollbarRect == null ? 0 : m_VerticalScrollbarRect.rect.width);
        }

        protected override void OnEnable()
        {
            base.OnEnable();

            if (m_HorizontalScrollbar)
                m_HorizontalScrollbar.onValueChanged.AddListener(SetHorizontalNormalizedPosition);
            if (m_VerticalScrollbar)
                m_VerticalScrollbar.onValueChanged.AddListener(SetVerticalNormalizedPosition);

            CanvasUpdateRegistry.RegisterCanvasElementForLayoutRebuild(this);
        }

        protected override void OnDisable()
        {
            CanvasUpdateRegistry.UnRegisterCanvasElementForRebuild(this);

            if (m_HorizontalScrollbar)
                m_HorizontalScrollbar.onValueChanged.RemoveListener(SetHorizontalNormalizedPosition);
            if (m_VerticalScrollbar)
                m_VerticalScrollbar.onValueChanged.RemoveListener(SetVerticalNormalizedPosition);

            m_HasRebuiltLayout = false;
            m_Tracker.Clear();
            m_Velocity = Vector2.zero;
            LayoutRebuilder.MarkLayoutForRebuild(rectTransform);
            base.OnDisable();
        }

        public override bool IsActive()
        {
            return base.IsActive() && m_Content != null;
        }

        private void EnsureLayoutHasRebuilt()
        {
            if (!m_HasRebuiltLayout && !CanvasUpdateRegistry.IsRebuildingLayout())
                Canvas.ForceUpdateCanvases();
        }

        public virtual void StopMovement()
        {
            m_Velocity = Vector2.zero;
        }

        public virtual void OnScroll(PointerEventData data)
        {
            if (!IsActive())
                return;

            EnsureLayoutHasRebuilt();
            UpdateBounds();

            Vector2 delta = data.scrollDelta;
            delta.y *= -1;
            if (vertical && !horizontal)
            {
                if (Mathf.Abs(delta.x) > Mathf.Abs(delta.y))
                    delta.y = delta.x;
                delta.x = 0;
            }
            if (horizontal && !vertical)
            {
                if (Mathf.Abs(delta.y) > Mathf.Abs(delta.x))
                    delta.x = delta.y;
                delta.y = 0;
            }

            Vector2 position = m_Content.anchoredPosition;
            position += delta * m_ScrollSensitivity;
            if (m_MovementType == MovementType.Clamped)
                position += CalculateOffset(position - m_Content.anchoredPosition);

            SetContentAnchoredPosition(position);
            UpdateBounds();
        }

        public virtual void OnInitializePotentialDrag(PointerEventData eventData)
        {
            if (eventData.button != PointerEventData.InputButton.Left)
                return;

            m_Velocity = Vector2.zero;
        }

        public virtual void OnBeginDrag(PointerEventData eventData)
        {
            if (eventData.button != PointerEventData.InputButton.Left)
                return;

            if (!IsActive())
                return;

            UpdateBounds();

            m_PointerStartLocalCursor = Vector2.zero;
            RectTransformUtility.ScreenPointToLocalPointInRectangle(viewRect, eventData.position, eventData.pressEventCamera, out m_PointerStartLocalCursor);
            m_ContentStartPosition = m_Content.anchoredPosition;
            m_Dragging = true;
        }

        public virtual void OnEndDrag(PointerEventData eventData)
        {
            if (eventData.button != PointerEventData.InputButton.Left)
                return;

            m_Dragging = false;
            onDragEnded?.Invoke();

            if (scrollToCenterProperties.enable)
            {
                ScrollToCenter();
            }
        }

        public virtual void OnDrag(PointerEventData eventData)
        {
            if (eventData.button != PointerEventData.InputButton.Left)
                return;

            if (!IsActive())
                return;

            if (canDrag != null && !canDrag(eventData.delta))
            {
                m_PointerStartLocalCursor = Vector2.zero;
                RectTransformUtility.ScreenPointToLocalPointInRectangle(viewRect, eventData.position, eventData.pressEventCamera, out m_PointerStartLocalCursor);
                m_ContentStartPosition = m_Content.anchoredPosition;
                return;
            }

            Vector2 localCursor;
            if (!RectTransformUtility.ScreenPointToLocalPointInRectangle(viewRect, eventData.position, eventData.pressEventCamera, out localCursor))
                return;


            UpdateBounds();

            var pointerDelta = localCursor - m_PointerStartLocalCursor;
            Vector2 position = m_ContentStartPosition + pointerDelta;

            Vector2 offset = CalculateOffset(position - m_Content.anchoredPosition);

            position += offset;
            if (m_MovementType == MovementType.Elastic)
            {
                if (offset.x != 0)
                    position.x = position.x - RubberDelta(offset.x, m_ViewBounds.size.x);
                if (offset.y != 0)
                    position.y = position.y - RubberDelta(offset.y, m_ViewBounds.size.y);
            }

            SetContentAnchoredPosition(position);
        }

        public void OnPointerEnter(PointerEventData eventData)
        {
            autoScrollProperties.enable = false;
        }

        public void OnPointerExit(PointerEventData eventData)
        {
            if (lastAutoScroll)
            {
                autoScrollProperties.enable = true;
            }
        }

        protected virtual void SetContentAnchoredPosition(Vector2 position)
        {
            if (!m_Horizontal)
                position.x = m_Content.anchoredPosition.x;
            if (!m_Vertical)
                position.y = m_Content.anchoredPosition.y;

            if (position != m_Content.anchoredPosition)
            {
                m_Content.anchoredPosition = position;
                UpdateBounds();
            }
        }

        Vector2 offset = Vector2.zero;
        Vector2 position = Vector2.zero;
        Vector3 newVelocity = Vector3.zero;

        protected virtual void LateUpdate()
        {
            if (prefabPool == null)
                return;
            if (!m_Content)
                return;
            if (initInStart == false)
                return;
            EnsureLayoutHasRebuilt();
            UpdateScrollbarVisibility();
            UpdateBounds();
            float deltaTime = Time.unscaledDeltaTime;
            offset = CalculateOffset(Vector2.zero);
            bool offsetbool = ComparisonVector2(offset, Vector2.zero);
            bool velocitybool = ComparisonVector2(m_Velocity, Vector2.zero);
            if (!m_Dragging && (!offsetbool || !velocitybool))
            {
                position = m_Content.anchoredPosition;
                for (int axis = 0; axis < 2; axis++)
                {
                    if (m_MovementType == MovementType.Elastic && offset[axis] != 0)
                    {
                        float speed = m_Velocity[axis];
                        position[axis] = Mathf.SmoothDamp(m_Content.anchoredPosition[axis], m_Content.anchoredPosition[axis] + offset[axis], ref speed, m_Elasticity, Mathf.Infinity, deltaTime);
                        m_Velocity[axis] = speed;
                    }
                    else if (m_Inertia)
                    {
                        m_Velocity[axis] *= Mathf.Pow(m_DecelerationRate, deltaTime);
                        if (Mathf.Abs(m_Velocity[axis]) < 1)
                            m_Velocity[axis] = 0;
                        position[axis] += m_Velocity[axis] * deltaTime;
                    }
                    else
                    {
                        m_Velocity[axis] = 0;
                    }
                }

                bool velocity = ComparisonVector2(m_Velocity, Vector2.zero);
                if (!velocity)
                {
                    if (m_MovementType == MovementType.Clamped)
                    {
                        offset = CalculateOffset(position - m_Content.anchoredPosition);
                        position += offset;
                    }
                    SetContentAnchoredPosition(position);
                }

                offsetbool = ComparisonVector2(offset, Vector2.zero);
                velocitybool = ComparisonVector2(m_Velocity, Vector2.zero);
                if (offsetbool && velocitybool)
                {
                    onDragFinished?.Invoke();
                }

            }

            if (m_Dragging && m_Inertia)
            {
                newVelocity = (m_Content.anchoredPosition - m_PrevPosition) / deltaTime;
                m_Velocity = Vector3.Lerp(m_Velocity, newVelocity, deltaTime * 10);
            }

            if (m_ViewBounds != m_PrevViewBounds || m_ContentBounds != m_PrevContentBounds || m_Content.anchoredPosition != m_PrevPosition)
            {
                UpdateScrollbars(offset);
                m_OnValueChanged.Invoke(normalizedPosition);
                UpdatePrevData();
            }

            ChangeArriveIdentity();
        }

        private bool ComparisonVector2(Vector2 v1, Vector2 v2)
        {
            if ((v1 - v2).sqrMagnitude < 0.0001)
                return true;
            return false;
        }

        private Vector3[] mCorners = new Vector3[4];

        public Vector3[] worldCorners
        {
            get
            {
                Vector2 size = thisRect.sizeDelta;
                float x0 = -0.5f * size.x;
                float y0 = -0.5f * size.y;
                float x1 = x0 + size.x;
                float y1 = y0 + size.y;

                Vector3[] localvector3 = new Vector3[4];

                localvector3[0] = new Vector3(x0, y0);
                localvector3[1] = new Vector3(x0, y1);
                localvector3[2] = new Vector3(x1, y1);
                localvector3[3] = new Vector3(x1, y0);

                Vector3 localcenter = thisRect.anchoredPosition3D;
                localvector3[0] += localcenter;
                localvector3[1] += localcenter;
                localvector3[2] += localcenter;
                localvector3[3] += localcenter;

                mCorners[0] = transform.TransformPoint(localvector3[0]);
                mCorners[1] = transform.TransformPoint(localvector3[1]);
                mCorners[2] = transform.TransformPoint(localvector3[2]);
                mCorners[3] = transform.TransformPoint(localvector3[3]);

                return mCorners;
            }
        }

        private void UpdatePrevData()
        {
            if (m_Content == null)
                m_PrevPosition = Vector2.zero;
            else
                m_PrevPosition = m_Content.anchoredPosition;
            m_PrevViewBounds = m_ViewBounds;
            m_PrevContentBounds = m_ContentBounds;
        }

        private void UpdateScrollbars(Vector2 offset)
        {
            if (m_HorizontalScrollbar)
            {
                if (m_ContentBounds.size.x > 0)
                    m_HorizontalScrollbar.size = Mathf.Clamp01((m_ViewBounds.size.x - Mathf.Abs(offset.x)) / m_ContentBounds.size.x);
                else
                    m_HorizontalScrollbar.size = 1;

                m_HorizontalScrollbar.value = horizontalNormalizedPosition;
            }

            if (m_VerticalScrollbar)
            {
                if (m_ContentBounds.size.y > 0)
                    m_VerticalScrollbar.size = Mathf.Clamp01((m_ViewBounds.size.y - Mathf.Abs(offset.y)) / m_ContentBounds.size.y);
                else
                    m_VerticalScrollbar.size = 1;

                m_VerticalScrollbar.value = verticalNormalizedPosition;
            }
        }

        public Vector2 normalizedPosition
        {
            get
            {
                return new Vector2(horizontalNormalizedPosition, verticalNormalizedPosition);
            }
            set
            {
                SetNormalizedPosition(value.x, 0);
                SetNormalizedPosition(value.y, 1);
            }
        }

        public float horizontalNormalizedPosition
        {
            get
            {
                UpdateBounds();
                if (m_ContentBounds.size.x <= m_ViewBounds.size.x)
                    return (m_ViewBounds.min.x > m_ContentBounds.min.x) ? 1 : 0;
                return (m_ViewBounds.min.x - m_ContentBounds.min.x) / (m_ContentBounds.size.x - m_ViewBounds.size.x);
            }
            set
            {
                SetNormalizedPosition(value, 0);
            }
        }

        public float verticalNormalizedPosition
        {
            get
            {
                UpdateBounds();
                if (m_ContentBounds.size.y <= m_ViewBounds.size.y)
                    return (m_ViewBounds.min.y > m_ContentBounds.min.y) ? 1 : 0;
                ;
                return (m_ViewBounds.min.y - m_ContentBounds.min.y) / (m_ContentBounds.size.y - m_ViewBounds.size.y);
            }
            set
            {
                SetNormalizedPosition(value, 1);
            }
        }

        private void SetHorizontalNormalizedPosition(float value) { SetNormalizedPosition(value, 0); }
        private void SetVerticalNormalizedPosition(float value) { SetNormalizedPosition(value, 1); }

        private void SetNormalizedPosition(float value, int axis)
        {
            EnsureLayoutHasRebuilt();
            UpdateBounds();
            float hiddenLength = m_ContentBounds.size[axis] - m_ViewBounds.size[axis];
            float contentBoundsMinPosition = m_ViewBounds.min[axis] - value * hiddenLength;
            float newLocalPosition = m_Content.localPosition[axis] + contentBoundsMinPosition - m_ContentBounds.min[axis];

            Vector3 localPosition = m_Content.localPosition;
            if (Mathf.Abs(localPosition[axis] - newLocalPosition) > 0.01f)
            {
                localPosition[axis] = newLocalPosition;
                m_Content.localPosition = localPosition;
                m_Velocity[axis] = 0;
                UpdateBounds();
            }
        }

        private static float RubberDelta(float overStretching, float viewSize)
        {
            return (1 - (1 / ((Mathf.Abs(overStretching) * 0.55f / viewSize) + 1))) * viewSize * Mathf.Sign(overStretching);
        }

        protected override void OnRectTransformDimensionsChange()
        {
            SetDirty();
        }

        private bool hScrollingNeeded
        {
            get
            {
                if (Application.isPlaying)
                    return m_ContentBounds.size.x > m_ViewBounds.size.x + 0.01f;
                return true;
            }
        }
        private bool vScrollingNeeded
        {
            get
            {
                if (Application.isPlaying)
                    return m_ContentBounds.size.y > m_ViewBounds.size.y + 0.01f;
                return true;
            }
        }

        public virtual void CalculateLayoutInputHorizontal() { }
        public virtual void CalculateLayoutInputVertical() { }

        public virtual float minWidth { get { return -1; } }
        public virtual float preferredWidth { get { return -1; } }
        public virtual float flexibleWidth { get; private set; }

        public virtual float minHeight { get { return -1; } }
        public virtual float preferredHeight { get { return -1; } }
        public virtual float flexibleHeight { get { return -1; } }

        public virtual int layoutPriority { get { return -1; } }

        public virtual void SetLayoutHorizontal()
        {
            m_Tracker.Clear();

            if (m_HSliderExpand || m_VSliderExpand)
            {
                m_Tracker.Add(this, viewRect,
                    DrivenTransformProperties.Anchors |
                    DrivenTransformProperties.SizeDelta |
                    DrivenTransformProperties.AnchoredPosition);

                viewRect.anchorMin = Vector2.zero;
                viewRect.anchorMax = Vector2.one;
                viewRect.sizeDelta = Vector2.zero;
                viewRect.anchoredPosition = Vector2.zero;

                LayoutRebuilder.ForceRebuildLayoutImmediate(content);
                m_ViewBounds = new Bounds(viewRect.rect.center, viewRect.rect.size);
                m_ContentBounds = GetBounds();
            }

            if (m_VSliderExpand && vScrollingNeeded)
            {
                viewRect.sizeDelta = new Vector2(-(m_VSliderWidth + m_VerticalScrollbarSpacing), viewRect.sizeDelta.y);

                LayoutRebuilder.ForceRebuildLayoutImmediate(content);
                m_ViewBounds = new Bounds(viewRect.rect.center, viewRect.rect.size);
                m_ContentBounds = GetBounds();
            }

            if (m_HSliderExpand && hScrollingNeeded)
            {
                viewRect.sizeDelta = new Vector2(viewRect.sizeDelta.x, -(m_HSliderHeight + m_HorizontalScrollbarSpacing));
                m_ViewBounds = new Bounds(viewRect.rect.center, viewRect.rect.size);
                m_ContentBounds = GetBounds();
            }

            if (m_VSliderExpand && vScrollingNeeded && viewRect.sizeDelta.x == 0 && viewRect.sizeDelta.y < 0)
            {
                viewRect.sizeDelta = new Vector2(-(m_VSliderWidth + m_VerticalScrollbarSpacing), viewRect.sizeDelta.y);
            }
        }

        public virtual void SetLayoutVertical()
        {
            UpdateScrollbarLayout();
            m_ViewBounds = new Bounds(viewRect.rect.center, viewRect.rect.size);
            m_ContentBounds = GetBounds();
        }

        void UpdateScrollbarVisibility()
        {
            if (m_VerticalScrollbar && m_VerticalScrollbarVisibility != ScrollbarVisibility.Permanent && m_VerticalScrollbar.gameObject.activeSelf != vScrollingNeeded)
                m_VerticalScrollbar.gameObject.SetActive(vScrollingNeeded);

            if (m_HorizontalScrollbar && m_HorizontalScrollbarVisibility != ScrollbarVisibility.Permanent && m_HorizontalScrollbar.gameObject.activeSelf != hScrollingNeeded)
                m_HorizontalScrollbar.gameObject.SetActive(hScrollingNeeded);
        }

        void UpdateScrollbarLayout()
        {
            if (m_VSliderExpand && m_HorizontalScrollbar)
            {
                m_Tracker.Add(this, m_HorizontalScrollbarRect,
                              DrivenTransformProperties.AnchorMinX |
                              DrivenTransformProperties.AnchorMaxX |
                              DrivenTransformProperties.SizeDeltaX |
                              DrivenTransformProperties.AnchoredPositionX);
                m_HorizontalScrollbarRect.anchorMin = new Vector2(0, m_HorizontalScrollbarRect.anchorMin.y);
                m_HorizontalScrollbarRect.anchorMax = new Vector2(1, m_HorizontalScrollbarRect.anchorMax.y);
                m_HorizontalScrollbarRect.anchoredPosition = new Vector2(0, m_HorizontalScrollbarRect.anchoredPosition.y);
                if (vScrollingNeeded)
                    m_HorizontalScrollbarRect.sizeDelta = new Vector2(-(m_VSliderWidth + m_VerticalScrollbarSpacing), m_HorizontalScrollbarRect.sizeDelta.y);
                else
                    m_HorizontalScrollbarRect.sizeDelta = new Vector2(0, m_HorizontalScrollbarRect.sizeDelta.y);
            }

            if (m_HSliderExpand && m_VerticalScrollbar)
            {
                m_Tracker.Add(this, m_VerticalScrollbarRect,
                              DrivenTransformProperties.AnchorMinY |
                              DrivenTransformProperties.AnchorMaxY |
                              DrivenTransformProperties.SizeDeltaY |
                              DrivenTransformProperties.AnchoredPositionY);
                m_VerticalScrollbarRect.anchorMin = new Vector2(m_VerticalScrollbarRect.anchorMin.x, 0);
                m_VerticalScrollbarRect.anchorMax = new Vector2(m_VerticalScrollbarRect.anchorMax.x, 1);
                m_VerticalScrollbarRect.anchoredPosition = new Vector2(m_VerticalScrollbarRect.anchoredPosition.x, 0);
                if (hScrollingNeeded)
                    m_VerticalScrollbarRect.sizeDelta = new Vector2(m_VerticalScrollbarRect.sizeDelta.x, -(m_HSliderHeight + m_HorizontalScrollbarSpacing));
                else
                    m_VerticalScrollbarRect.sizeDelta = new Vector2(m_VerticalScrollbarRect.sizeDelta.x, 0);
            }
        }

        protected void UpdateBounds(bool updateItems = true)
        {
            m_ViewBounds = new Bounds(viewRect.rect.center, viewRect.rect.size);
            m_ContentBounds = GetBounds();

            if (m_Content == null)
                return;

            // ============LoopScrollRect============
            if (Application.isPlaying && updateItems && UpdateItems(m_ViewBounds, m_ContentBounds))
            {
                Canvas.ForceUpdateCanvases();
                m_ContentBounds = GetBounds();
            }
            // ============LoopScrollRect============

            Vector3 contentSize = m_ContentBounds.size;
            Vector3 contentPos = m_ContentBounds.center;
            Vector3 excess = m_ViewBounds.size - contentSize;
            if (excess.x > 0)
            {
                contentPos.x -= excess.x * (m_Content.pivot.x - 0.5f);
                contentSize.x = m_ViewBounds.size.x;
            }
            if (excess.y > 0)
            {
                contentPos.y -= excess.y * (m_Content.pivot.y - 0.5f);
                contentSize.y = m_ViewBounds.size.y;
            }

            m_ContentBounds.size = contentSize;
            m_ContentBounds.center = contentPos;
        }

        private readonly Vector3[] m_Corners = new Vector3[4];
        protected Bounds GetBounds()
        {
            if (m_Content == null)
                return new Bounds();

            var vMin = new Vector3(float.MaxValue, float.MaxValue, float.MaxValue);
            var vMax = new Vector3(float.MinValue, float.MinValue, float.MinValue);

            var toLocal = viewRect.worldToLocalMatrix;
            m_Content.GetWorldCorners(m_Corners);
            for (int j = 0; j < 4; j++)
            {
                Vector3 v = toLocal.MultiplyPoint3x4(m_Corners[j]);
                vMin = Vector3.Min(v, vMin);
                vMax = Vector3.Max(v, vMax);
            }

            var bounds = new Bounds(vMin, Vector3.zero);
            bounds.Encapsulate(vMax);
            return bounds;
        }

        private Vector2 CalculateOffset(Vector2 delta)
        {
            Vector2 offset = Vector2.zero;
            if (m_MovementType == MovementType.Unrestricted)
                return offset;

            Vector2 min = m_ContentBounds.min;
            Vector2 max = m_ContentBounds.max;

            if (m_Horizontal)
            {
                min.x += delta.x;
                max.x += delta.x;
                if (min.x > m_ViewBounds.min.x)
                    offset.x = m_ViewBounds.min.x - min.x;
                else if (max.x < m_ViewBounds.max.x)
                    offset.x = m_ViewBounds.max.x - max.x;
            }

            if (m_Vertical)
            {
                min.y += delta.y;
                max.y += delta.y;
                if (max.y < m_ViewBounds.max.y)
                    offset.y = m_ViewBounds.max.y - max.y;
                else if (min.y > m_ViewBounds.min.y)
                    offset.y = m_ViewBounds.min.y - min.y;
            }

            return offset;
        }

        protected void SetDirty()
        {
            if (!IsActive())
                return;

            LayoutRebuilder.MarkLayoutForRebuild(rectTransform);
        }

        protected void SetDirtyCaching()
        {
            if (!IsActive())
                return;

            CanvasUpdateRegistry.RegisterCanvasElementForLayoutRebuild(this);
            LayoutRebuilder.MarkLayoutForRebuild(rectTransform);
        }

        private void ChangeArriveIdentity()
        {
            if (beforeTransform != null)
            {
                bool boo = itemTypeStart == 0 ? false : true;
                bool curboo = beforeTransform.gameObject.activeSelf;
                if (boo != curboo)
                {
                    beforeTransform.gameObject.SetActive(boo);
                }
            }

            if (afterTransform != null)
            {
                bool boo = itemTypeEnd == totalCount ? false : true;
                bool curboo = afterTransform.gameObject.activeSelf;
                if (boo != curboo)
                {
                    afterTransform.gameObject.SetActive(boo);
                }
            }

        }

#if UNITY_EDITOR
        protected override void OnValidate()
        {
            SetDirtyCaching();
        }
#endif

        public virtual void Add(int idx) { }

        public virtual void Remove(int idx) { }

        public virtual void Change(int idx) { }

        public virtual void Resume(int count, int startidx = 0) { }

        public virtual void Refresh(int idx) { }

        public virtual void Movement(int idx) { }
        public virtual void DoMovement(int idx) { }

    }
}